#include "include/muduo/proto/dispatcher.h"
#include "include/muduo/proto/codec.h"
#include "protocol.pb.h"

#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/base/CountDownLatch.h"

#include <iostream>
#include <memory>
#include <functional>
#include <string>
#include <unordered_map>
#include <unistd.h>

class PbClient
{
public:
    using TransRespPtr = std::shared_ptr<notbean::TranslateResponse>;
    using AddRespPtr = std::shared_ptr<notbean::AddResponse>;
public:
    PbClient(const std::string& serverIp, uint16_t serverPort)
        : _latch(1), _client(_loopThread.startLoop(), muduo::net::InetAddress(serverIp, serverPort), "PbClient")
        , _dispatcher(std::bind(&PbClient::onUnknownMessage, this, 
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
        , _codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, 
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
    {
        _dispatcher.registerMessageCallback<notbean::TranslateResponse>(std::bind(&PbClient::onTranslate, this, 
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _dispatcher.registerMessageCallback<notbean::AddResponse>(std::bind(&PbClient::onAdd, this, 
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _client.setConnectionCallback(std::bind(&PbClient::onCinnection, this,
            std::placeholders::_1));
        _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, 
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    }

    void connect()
    {
        _client.connect();
        _latch.wait();
    }

    void use()
    {
        while(true)
        {
            char op;
            std::cout << "choose: t to translate, a to add> ";
            std::cin >> op;
            if(op == 't') {
                std::string word;
                std::cout << "input word> ";
                std::cin >> word;
                translate(word);
            } else if(op == 'a') {
                int op1;
                int op2;
                std::cout << "input op1> ";
                std::cin >> op1;
                std::cout << "input op2> ";
                std::cin >> op2;
                add(op1, op2);
            }
            usleep(500);
        }
    }

    ~PbClient()
    {}
private:
    void translate(const std::string& word)
    {
        notbean::TranslateRequest req;
        req.set_msg(word);
        send(&req); //向上转型
    }

    void add(int op1, int op2)
    {
        notbean::AddRequest req;
        req.set_op1(op1);
        req.set_op2(op2);
        send(&req); //向上转型
    }

    bool send(google::protobuf::Message* req)
    {
        if(!_conn->connected()) return false; //连接异常，不发送
        _codec.send(_conn, *req);
        return true;
    }
    
    void onCinnection(const muduo::net::TcpConnectionPtr& conn)
    {
        if(conn->connected()) 
        {
            _conn = conn;
            _latch.countDown();
            std::cout << "connection established..." << std::endl;
        }
        else 
        {
            std::cout << "connection closed..." << std::endl;
        }
    }

    void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp timestamp)
    {
        std::cout << "unknown message: " << message->GetTypeName() << std::endl;
        conn->shutdown();
    }

    void onTranslate(const muduo::net::TcpConnectionPtr& conn, const TransRespPtr& message, muduo::Timestamp timestamp)
    {
        std::cout << "translate result: " << message->msg() << std::endl;
    }

    void onAdd(const muduo::net::TcpConnectionPtr& conn, const AddRespPtr& message, muduo::Timestamp timestamp)
    {
        std::cout << "add result: " << message->result() << std::endl;
    }
private:
    muduo::CountDownLatch _latch;
    muduo::net::EventLoopThread _loopThread;
    muduo::net::TcpConnectionPtr _conn;
    muduo::net::TcpClient _client;
    ProtobufDispatcher _dispatcher;
    ProtobufCodec _codec;
};

int main()
{
    std::unique_ptr<PbClient> clt = std::make_unique<PbClient>("127.0.0.1", 9090);
    clt->connect();
    clt->use();

    return 0;
}